import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;
import java.util.Vector;

import soot.Body;
import soot.PhaseOptions;
import soot.Scene;
import soot.SceneTransformer;
import soot.SootClass;
import soot.SootMethod;
import soot.Unit;
import soot.jimple.toolkits.callgraph.CHATransformer;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Targets;
import soot.tagkit.LineNumberTag;
import soot.tagkit.Tag;
import soot.toolkits.graph.BriefUnitGraph;
import soot.toolkits.graph.TrapUnitGraph;
import soot.toolkits.graph.UnitGraph;

public class PathTransform extends SceneTransformer {

	private SootMethod Smethod;
	private Vector<String> files;
	private String startclassindex;
	private String startclass;
	private String startline;
	private String endclassindex;
	private String endclass;
	private String endline;
	private String endmethod;
	private String startmethod;
	private String StartMethodDetail;

	public PathTransform(Vector<String> files, String startClass,
			String startLine, String endClass, String endLine) {
		this.files = files;

		this.startclassindex = startClass;
		this.startline = startLine;

		this.endclassindex = endClass;
		this.endline = endLine;
	}

	@Override
	protected void internalTransform(String phaseName, Map options) {
		CHATransformer.v().transform();
		PhaseOptions.v().setPhaseOption("tag.ln", "on");
		// find the start method and class
		// find the end method and class
		processClassInfo();
		System.out.println("startclass:" + startclass);
		System.out.println("startmethod:" + startmethod);
		System.out.println("startline:" + startline);
		System.out.println("endclass:" + endclass);
		System.out.println("endline:" + endline);
		// Get the Call Graph cg
		System.out.println("start call graph!!!!");

		if (Smethod == null
				|| !Scene.v().containsMethod(Smethod.getSignature())) {
			System.out.println("method is not invoked by the current program");
		} else {
			CallGraph cg = Scene.v().getCallGraph();
			IndexContainer.init(cg, files);

			// get the start method
			IndexMethod startMethod = IndexContainer
					.getMethod(StartMethodDetail);

			// the following line could be used to output the index data

			// OutputStream f1;
			// try {
			// f1 = new FileOutputStream("d:/index.txt");
			// f1.write(IndexContainer.getString().getBytes());
			// } catch (FileNotFoundException e) {
			// e.printStackTrace();
			// } catch (IOException e) {
			// e.printStackTrace();
			// }

			PathContainer container = new PathContainer(startclassindex + "::"
					+ startline, endclassindex + "::" + endline, startMethod,
					cg);
			container.process();
		}
	}

	private void processClassInfo() {
		// TODO Auto-generat
		Vector<String> startclasses = ClassNameIndex.getClassName(Integer
				.parseInt(startclassindex));
		Vector<String> endclasses = ClassNameIndex.getClassName(Integer
				.parseInt(endclassindex));
		processPathInfo(startclasses, endclasses, startline, endline);
	}

	private void processPathInfo(Vector<String> startclasses,
			Vector<String> endclasses, String startline, String endline) {
		processStartClass(startclasses, startline);
		processEndClass(endclasses, endline);
	}

	private void processEndClass(Vector<String> classes, String line) {
		for (int i = 0; i < classes.size(); i++) {
			Vector<String> result = getMethodandClassFromLine(line, classes
					.get(i), 1);
			if (result.size() == 0) {
				continue;
			} else {
				this.endclass = result.get(0);
				this.endmethod = result.get(1);
				break;
			}
		}
	}

	private void processStartClass(Vector<String> classes, String line) {
		for (int i = 0; i < classes.size(); i++) {
			Vector<String> result = getMethodandClassFromLine(line, classes
					.get(i), 0);
			if (result.size() == 0) {
				continue;
			} else {
				this.startclass = result.get(0);
				this.startmethod = result.get(1);
				break;
			}
		}
	}

	/***
	 * get the class name according to the specific line number
	 * 
	 * @param line
	 * @param className
	 * @return
	 */
	private Vector<String> getMethodandClassFromLine(String line,
			String className, int flag) {
		Vector<String> results = new Vector<String>();
		System.out.println(className);
		if (!Scene.v().containsClass(className)) {
			System.out.println("not contains");
		} else {
			SootClass s = Scene.v().loadClassAndSupport(className);
			s.setApplicationClass();
			Iterator methodIt = s.getMethods().iterator();
			boolean found = false;
			while (methodIt.hasNext() && !found) {
				SootMethod m = (SootMethod) methodIt.next();
				if (!m.isConcrete()) {
					// abstract class , skip it
					continue;
				}
				Body b = m.retrieveActiveBody();
				// System.out.println("===============");
				// System.out.println(m.toString());

				// the first and last unit of the method
				// if the line is involved in the method.
				// the line should between the two units.
				Unit firstUnit = b.getUnits().getFirst();
				Unit lastUnit = b.getUnits().getLast();

				int firstLine = getLineFromUnit(firstUnit);
				int lastLine = getLineFromUnit(lastUnit);

				// System.out.println(firstLine + ":" + line + ":" + lastLine);

				if (firstLine <= Integer.parseInt(line)
						&& Integer.parseInt(line) <= lastLine) {
					results.add(className);
					results.add(m.toString().substring(
							m.toString().indexOf(":") + 2,
							m.toString().length() - 1));
					if (flag == 0) {
						StartMethodDetail = m.toString();
						Smethod = m;
					}
					found = true;
				}
			}
		}
		return results;
	}

	/***
	 * get the line number from the specific unit
	 * 
	 * @param unit
	 * @return
	 */
	private int getLineFromUnit(Unit unit) {
		for (Iterator j = unit.getTags().iterator(); j.hasNext();) {
			Tag tag = (Tag) j.next();
			if (tag instanceof LineNumberTag) {
				// byte[] value = tag.getValue();
				// int lineNumber = ((value[0] & 0xff) << 8) | (value[1] &
				// 0xff);
				LineNumberTag lnTag = (LineNumberTag) tag;
				int lineNumber = lnTag.getLineNumber();
				return lineNumber;
			}
		}
		return -1;
	}
}
